------------Tink's Adventure-----------
A 4am crack                  2015-08-11
-------------------. updated 2023-09-09
                   |___________________

Name: Tink's Adventure by Mercer Mayer
Genre: educational
Year: 1984
Authors: Angelsoft, Inc.
Publisher: Mindscape
Media: single-sided 5.25-inch floppy
OS: custom
Previous cracks: none
Similar cracks:
  Mathematics Today (crack no. 347)

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error, but it
  gets a participation medal just for
  showing up

Locksmith Fast Disk Backup
  unable to read any track

EDD 4 bit copy (no sync, no count)
  no errors, but the copy just boots,
  briefly ponders the meaning of life,
  then reboots (doesn't sound like it
  ever gets off track $00)

Copy ][+ nibble editor
  all tracks use standard prologues
  (address: D5 AA 96, data: D5 AA AD)
  but modified epilogues
  (address: FF FF FF, data: FF FF FF)

Disk Fixer
  ["O" -> "Input/Output Control"]
    set Address Epilogue to "FF FF FF"
    set Data Epilogue to "FF FF FF"
  Success! All tracks readable!
  T00 -> DOS 3.3 bootloader and RWTS
  No sign of a disk catalog anywhere
  No sign of the rest of DOS 3.3
  No sign of intelligent life anywhere

Why didn't COPYA work?
  modified epilogue bytes (every track)

Why didn't Locksmith FDB work?
  modified epilogue bytes (every track)

Why didn't my EDD copy work?
  Probably a nibble check during boot.
  Definitely a nibble check. Disks do
  not spontaneously reboot unless
  someone tells them to. That is not
  a thing that happens naturally. That
  is just not a thing.

Next steps:

  1. AUTOTRACE to capture RWTS
  2. Advanced Demuffin to convert the
     disk to a standard format
  3. Patch the RWTS (if necessary)
  4. Find nibble check and bypass it
  5. Declare victory(*)

(*) take a nap

                   ~

               Chapter 1
In Which We Attempt To Use The Original
    Disk As A Weapon Against Itself
     And Learn An Important Lesson
        About Determination And
         Believing In Yourself


[S6,D1=original disk]
[S6,D2=blank disk]
[S5,D1=my work disk]

]PR#5
CAPTURING BOOT0
...reboots slot 6...
...reboots slot 5...
SAVING BOOT0
CAPTURING BOOT1
...reboots slot 6...
...reboots slot 5...
SAVING BOOT1
SAVING RWTS
/!\ NIBBLE CHECK AT $BB00

]BRUN ADVANCED DEMUFFIN 1.5

["5" to switch to slot 5]

["R" to load a new RWTS module]
  --> At $B8, load "RWTS" from drive 1

["6" to switch to slot 6]

["C" to convert disk]

                 --v--

ADVANCED DEMUFFIN 1.5    (C) 1983, 2014
ORIGINAL BY THE STACK    UPDATES BY 4AM
=======PRESS ANY KEY TO CONTINUE=======
TRK:...................................
+.5:
    0123456789ABCDEF0123456789ABCDEF012
SC0:...................................
SC1:...................................
SC2:...................................
SC3:...................................
SC4:...................................
SC5:...................................
SC6:...................................
SC7:...................................
SC8:...................................
SC9:...................................
SCA:...................................
SCB:...................................
SCC:...................................
SCD:...................................
SCE:...................................
SCF:...................................
=======================================
16SC $00,$00-$22,$0F BY1.0 S6,D1->S6,D2

                 --^--

That took 23 tries. Attempts 1-10 were
full of read errors because the disk
was so dirty that it kept coating my
floppy drive with muck. Attempts 11-20
got the number of read errors down to
about five per rip, but my attempts to
stitch together a working copy (by
copying individual sectors from
different copies) failed. The RWTS had
reported that the reads had "succeeded"
because some bit on the weaker sectors
flipped in just the right combination
that the data field checksum passed.
Unsurprisingly, this crashed the game,
because executable code doesn't take
kindly to random bit flipping.

Floppies are dying, yo.

                   ~

               Chapter 2
       Hello $E7, My Old Friend
    I've Come To Talk To You Again


[S6,D1=demuffin'd copy]

]PR#6
...reboots endlessly...

]PR#5
...
]BLOAD BOOT1,A$2600
]CALL -151

*FE89G FE93G     ; disconnect DOS
*B600<2600.2FFFM ; move RWTS into place

*B700L

; well that's not normal
B700-   20 00 BB    JSR   $BB00

*BB00L

; this is the instruction I expected to
; find at $B700
BB00-   8E E9 B7    STX   $B7E9

; save zero page
BB03-   A2 00       LDX   #$00
BB05-   B5 00       LDA   $00,X
BB07-   9D 00 50    STA   $5000,X
BB0A-   CA          DEX
BB0B-   10 F8       BPL   $BB05

; don't know what this does yet...
BB0D-   20 30 BB    JSR   $BB30

; ...but apparently we care about the
; processor flags that it sets
BB10-   08          PHP

; restore zero page
BB11-   A2 00       LDX   #$00
BB13-   B9 00 50    LDA   $5000,Y
BB16-   95 00       STA   $00,X
BB18-   CA          DEX
BB19-   10 F8       BPL   $BB13

; restore processor flags
BB1B-   28          PLP

; "carry clear" = success
; (educated guess)
BB1C-   90 0F       BCC   $BB2D

; because now we're setting up a reboot
; via the stack
BB1E-   A5 2B       LDA   $2B
BB20-   4A          LSR
BB21-   4A          LSR
BB22-   4A          LSR
BB23-   4A          LSR
BB24-   09 C0       ORA   #$C0
BB26-   A8          TAY
BB27-   88          DEY
BB28-   98          TYA
BB29-   48          PHA
BB2A-   A9 FF       LDA   #$FF
BB2C-   48          PHA

; execution continues here regardless
BB2D-   A6 2B       LDX   $2B

; "return" to either $B703 (on success)
; or reboot (on failure)
BB2F-   60          RTS

; subroutine called from $BB0D
BB30-   A6 2B       LDX   $2B

; turn on drive motor manually
BB32-   BD 89 C0    LDA   $C089,X
BB35-   BD 8E C0    LDA   $C08E,X

; probably an address ($BBAE)
BB38-   A9 AE       LDA   #$AE
BB3A-   85 1E       STA   $1E
BB3C-   A9 BB       LDA   #$BB
BB3E-   85 1F       STA   $1F

; probably Death Counters
BB40-   A9 0A       LDA   #$0A
BB42-   85 09       STA   $09
BB44-   A9 80       LDA   #$80
BB46-   85 08       STA   $08

; if Death Counter hits 0, jump to
; The Badlands @ $BBA8
BB48-   C6 08       DEC   $08
BB4A-   F0 5C       BEQ   $BBA8

; get next address field
BB4C-   20 44 B9    JSR   $B944

; if that failed, off to The Badlands
BB4F-   B0 57       BCS   $BBA8

; check sector number (set by $B944)
BB51-   A5 2D       LDA   $2D

; is it the sector we wanted?
BB53-   C9 0A       CMP   #$0A

; nope, loop back and try again
BB55-   D0 F1       BNE   $BB48

; look for $D5 nibble
BB57-   A0 00       LDY   #$00
BB59-   BD 8C C0    LDA   $C08C,X
BB5C-   10 FB       BPL   $BB59
BB5E-   88          DEY

; no $D5, off to The Badlands with you
BB5F-   F0 47       BEQ   $BBA8
BB61-   C9 D5       CMP   #$D5
BB63-   D0 F4       BNE   $BB59

; look for $E7 nibble
BB65-   A0 00       LDY   #$00
BB67-   BD 8C C0    LDA   $C08C,X
BB6A-   10 FB       BPL   $BB67
BB6C-   88          DEY

; no $E7, off to The Badlands with you
BB6D-   F0 39       BEQ   $BBA8
BB6F-   C9 E7       CMP   #$E7
BB71-   D0 F4       BNE   $BB67

; look for two more $E7 nibbles
BB73-   BD 8C C0    LDA   $C08C,X
BB76-   10 FB       BPL   $BB73
BB78-   C9 E7       CMP   #$E7

; no $E7, off to The Badlands with you
BB7A-   D0 2C       BNE   $BBA8
BB7C-   BD 8C C0    LDA   $C08C,X
BB7F-   10 FB       BPL   $BB7C
BB81-   C9 E7       CMP   #$E7

; no $E7, off to The Badlands with you
BB83-   D0 23       BNE   $BBA8

; kill some time to get out of sync
; with the "proper" start of nibbles
BB85-   BD 8D C0    LDA   $C08D,X
BB88-   A0 10       LDY   #$10
BB8A-   24 FF       BIT   $FF

A short digression here into some super
low-level disk stuff, because this
wasn't low-level enough already...

$E7 $E7 $E7 $E7. What would that nibble
sequence look like on disk? The answer
is, "It depends." $E7 in hexadecimal is
11100111 in binary, so here is the
simplest possible answer:

   |--E7--||--E7--||--E7--||--E7--|
   11100111111001111110011111100111

But wait. Every nibble read from disk
must have its high bit set. In theory,
you could insert one or two "0" bits
after any of those nibbles. (Two is the
maximum, due to hardware limitations.)
These extra "0" bits would be swallowed
by the standard "wait for data latch to
have its high bit set" loop, which you
see over and over in any RWTS code:

  :1   LDA $C08C,X
       BPL :1

Now consider the following bitstream:

  |--E7--| |--E7--|  |--E7--||--E7--|
  11100111011100111001110011111100111
          ^        ^^
       (extra)   (extra)

The first $E7 has one extra "0" bit
after it, and the second $E7 has two
extra "0" bits after it. Totally legal,
works on any Apple II computer and any
floppy drive. A "LDA $C08C,X; BPL" loop
would still interpret this bitstream as
a sequence of four $E7 nibbles. Each of
the extra "0" bits appear after we've
just read a nibble and we're waiting
for the high bit to be set again.

Now, what if we miss the first few bits
of this bitstream, then start looking?
The disk is always spinning, whether
we're reading from it or not. If we
waste too much time doing something
other than reading, we'll literally
miss some bits as the disk spins by.
This is why the timing of low-level
RWTS code is so critical.

Let's say we waste 12 CPU cycles before
we start reading this bitstream. Each
bit takes 4 CPU cycles to go by, so
after 12 cycles, we would have missed
the first 3 bits (marked with an X).

            (normal start)

  |--E7--| |--E7--|  |--E7--||--E7--|
  11100111011100111001110011111100111
  XXX  |--EE--| |--E7--|  |--FC--|

           (delayed start)

Ah! It's interpreted as a completely
different nibble sequence if you delay
just a few CPU cycles before you start
reading. Also note that some of those
"extra" bits are no longer being
ignored; now they're being interpreted
as data, as part of the nibbles that
are being returned to the higher level
code. Meanwhile, other bits that were
part of the $E7 nibbles are now being
swallowed.

Now, let's go back to the first stream,
which had no extra bits between the
nibbles, and see what happens when we
waste those same 12 CPU cycles.

           (normal start)

   |--E7--||--E7--||--E7--||--E7--|
   11100111111001111110011111100111
   XXX  |--FC--||--FC--||--FC--|

          (delayed start)

After skipping the first three bits,
the stream is interpreted as a series
of $FC $FC $FC repeating endlessly --
not $EE $E7 $FC like the other stream.

Here's the kicker: generic bit copiers
didn't preserve these extra "0" bits
between nibbles. By "desynchronizing"
(wasting just the right number of CPU
cycles at just the right time), then
interpreting the bits on the disk in
mid-stream, developers could determine
at runtime whether you had an original
disk. Which is precisely the code we
just saw.

Here is the complete "E7 bitstream,"
annotated to show both the synchronized
and desynchronized nibble sequences.

 |--E7--| |--E7--|  |--E7--||--E7--|
 111001110111001110011100111111001110
 XXX  |--EE--| |--E7--|  |--FC--||--E

 |--E7--|  |--E7--||--E7--| |--E7--|
 111001110011100111111001110111001110
 E--| |--E7--|  |--FC--||--EE--| |--E

 |--E7--||--E7--|
 1110011111100111
 E--| |--FC--|

We now return you to the actual code...

                   ~

               Chapter 3
     In Which Things Are Restored
        To Their Proper Place,
        And Our Adventure Ends


*BB8CL

; now start looking for nibbles that
; don't really exist (except they do,
; because we're out of sync and reading
; timing bits as data)
BB8C-   BD 8C C0    LDA   $C08C,X
BB8F-   10 FB       BPL   $BB8C
BB91-   88          DEY
BB92-   F0 14       BEQ   $BBA8
BB94-   C9 EE       CMP   #$EE
BB96-   D0 F4       BNE   $BB8C

; compare the next 8 nibbles to an
; array stored at ($1E) [= $BBAE]
; in reverse order
BB98-   A0 07       LDY   #$07
BB9A-   BD 8C C0    LDA   $C08C,X
BB9D-   10 FB       BPL   $BB9A
BB9F-   D1 1E       CMP   ($1E),Y

; if any nibble doesn't match, off to
; The Badlands
BBA1-   D0 05       BNE   $BBA8
BBA3-   88          DEY
BBA4-   10 F4       BPL   $BB9A

; success falls through to here -- we
; are now satisfied that the disk is
; original, so clear the carry and
; return (to $BB1C)
BBA6-   18          CLC
BBA7-   60          RTS

; The Badlands (all failures go here)
; decrement the Death Counter and
; eventually give up, set the carry,
; and return (to $BB1C)
BBA8-   C6 09       DEC   $09
BBAA-   D0 98       BNE   $BB44
BBAC-   38          SEC
BBAD-   60          RTS

Finally, the array of desynchronized
nibbles (the E7 bitstream, in reverse
order):

BBAE-  [FC EE EE FC E7 EE FC E7]

This nibble check has no side effects.
I can simply bypass it by patching
$B700 to restore the STX instruction
that was supposed to be there in the
first place.

T00,S01,$00 change "2000BB" to "8EE9B7"

]PR#6
...works...

The RWTS is flexible enough to read my
copy, even in a standard format, so no
RWTS patches are required.

Quod erat liberandum.

                   ~

               Changelog


2023-09-09

- recracked from new source to fix some
  minor data corruption on track $1B

2015-08-11

- initial release

---------------------------------------
A 4am crack                     No. 401
------------------EOF------------------
